// i.d(t, { $: () => l })

import Observable from "./observable"
import { createObVal } from "./observable.value"
import funcUtil from "../utils/func.util"
function copyAllProperties(target, source) {
  const keys = Object.getOwnPropertyNames(source)
  for (const key of keys) {
    if (target[key] !== undefined) {
      continue
    }
    if (typeof source[key] === "function") {
      Object.defineProperty(target, key, {
        value: source[key].bind(target)
      })
    } else {
      Object.defineProperty(target, key, {
        value: source[key],
        writable: true
      })
    }
  }
  if (Object.getPrototypeOf(Object.getPrototypeOf(source)) !== null) {
    copyAllProperties(target, Object.getPrototypeOf(source))
  }
}

import ObservableObject from "./observable.object"

export default <T extends Object = {}>(target: T): T => {
  if (!target || Object.keys(target).length === 0) {
    throw new Error("createObservableProxy() only works on objects that has predefined keys. " + "Did you mean createObservableMap or createObservableArray?")
  }
  if (target instanceof ObservableObject) throw new Error("Using ObservableProxy on an ObservableObject is a BUG!")
  if ("isObservableProxy" in target) {
    return target
  }
  const propertyObservables: Record<any, any> = {}
  const mainObservable = new Observable()
  const proxy = Object.create(target)

  // Add any ObservableProxy specific properties
  Object.defineProperties(proxy, {
    isObservableProxy: {
      value: true
    },
    onPropertyChanged: {
      value(property, callback) {
        return propertyObservables[property].onChanged(callback)
      }
    },
    removeOnPropertyChanged: {
      value(property, callback) {
        return propertyObservables[property].removeOnChanged(callback)
      }
    },
    deepCopy: {
      value() {
        return funcUtil.deepCopy(target)
      }
    }
  })

  copyAllProperties(proxy, mainObservable)

  for (const key in target) {
    if (target.hasOwnProperty(key)) {
      const observable = createObVal(target[key])
      propertyObservables[key] = observable
      proxy.addChildObservable(observable)
      Object.defineProperty(proxy, key, {
        get: () => target[key],
        set: value => {
          target[key] = value
          propertyObservables[key].value = value
        },
        enumerable: true
      })
    }
  }

  Object.seal(proxy)
  return proxy
}
